<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="root">
    <div>{{name}}</div>
    <div>{{age}}</div>
    <div>hello2</div>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
  </div>
</body>
  <script>
    class Vnode {
      constructor(tag,data,value,type){
        this.tag = tag&&tag.toLowerCase();
        this.data = data;// 节点属性
        this.value = value;//文本内容
        this.type = type;
        this.children =[]
      }
      appendChild(vNode){
        this.children.push(vNode)
      }
    }
    
    function getVnode(node){
      let nodeType = node.nodeType;
      let _vnode = null;
      if(nodeType===1){
        let targName = node.nodeName;
        let attrs = node.attributes // attributes 返回是个伪数组
        let _attrObject = {}
        for(let i = 0;i<attrs.length;i++){ // attrs[i] 是属性节点
          _attrObject[attrs[i].nodeName] =attrs[i].nodeValue
        }
        _vnode = new Vnode(targName,_attrObject,undefined,nodeType)
        let childNodes = node.childNodes
        for(let i = 0 ;i<childNodes.length;i++){
          _vnode.appendChild(getVnode(childNodes[i]))
        }
      } else if(nodeType ===3){
        console.log('ss')
        _vnode = new Vnode(undefined,undefined,node.nodeValue,nodeType)
      }
      return _vnode
    }
    
    let kk = /\{\{(.+?)\}\}/g

    function getValueByPath (data, path) {
      let pathArr = path.split('.')
      let res = data;
      let prop;
      while (prop = pathArr.shift()) {
        res = res[prop]
      }
      return res
    }
    
    function paseVnode(vnode){
        let type = vnode.type
        let _node =null;
        if(type===3){
          return document.createTextNode(vnode.value)
        } else if(type ===1){
          _node = document.createElement(vnode.tag);
          //属性
          let data= vnode.data;
          Object.keys(data).forEach(item =>{
            let attrName = item;
            let attrValue = data[item];
            _node.setAttribute(attrName,attrValue);
          })
          //子元素
          let children = vnode.children;
          children.forEach(subvnode =>{
            _node.appendChild(paseVnode(subvnode))
          })

          return _node
        }
      }
    //  将带有坑的vnode 与data 生成新的vnode
    function combine (vnode,data) {
      let _tag = vnode.tag
      let _type = vnode.type;
      let _data = vnode.data;
      let _value = vnode.value;
      let _children = vnode.children;
      let _vnode =null;

      if(_type ===3){
        // 对文本处理
        _value = _value.replace(kk,function(_,g){
          return getValueByPath(data,g.trim())
        })
        _vnode = new Vnode(_tag,_data,_value,_type)
      } else if(_type===1){
        _vnode = new Vnode(_tag,_data,_value,_type);
        _children.forEach(_subVnode => _vnode.appendChild(combine(_subVnode,data)))
      }
      return _vnode
    }
      // 本接实现缓存 抽象语法树 ast
      // vue 使用了二次提交  
      // 1页面中的dom 与vnode 是一一对应的    引用类型
      // 2先ast 生成 和数据  render生成 新的 vnode
      // 3旧vonde 和新的vonde 比较diff  更新updata
    function Yvue (options) {
      this._options=options;
      this._data = options.data;
      this._elm = document.querySelector(options.el);
      
      this._template = this._elm;
      this._parent = this._elm.parentNode;
      this.mount();// 挂载
    }

    
    Yvue.prototype.mount = function(){

      //  需要提供个render 方法 生成虚拟dome
      this.render = this.createRenderFn()

      this.mountComponent();
    }
   
    Yvue.prototype.mountComponent = function(){
      // 执行mountComponent
      let mount = () =>{
        this.upData(this.render())
      }
      mount.call(this); // 本质交个watcher 来调用
    }

// 这里是生成render 函数目的是缓存抽象语法树 用虚拟dome 模拟
    Yvue.prototype.createRenderFn =function(){
      // 带坑的 生成vnode
      let ast = getVnode(this._template)
      // ast 与data 生成新的vnode   我们用 带坑的 vnode 与data 生成新的vnode
      return function render (){
        let temp = combine(ast,this._data)
        return temp
      }
    } 
// 将虚拟dome  渲染到dome 中 ：deff 算法就在这  
    Yvue.prototype.upData= function(vnode){
        // 简化 , 直接生成html dom 替换到页面中
        let raldom = paseVnode(vnode);
        this._parent.replaceChild(raldom,document.querySelector(this._options.el))
    }
   
   let app = new Yvue({
     el:'#root',
     data:{
       name:'zhangsang',
       age:20,
     }
   })
   
 
  </script>
</html>